<template>
    <div class="board" @keydown.w="upStart" @keyup.w="upEnd">
        <div class="roles">
            <div class="left"><div class="name">剩余:</div><div class="current">{{ leftEnemies }}</div><div class="total">{{ totalEnemies }}</div></div>
            <div class="left"><div class="name">STAGE</div><div class="current">{{ board.stage }}</div></div>
            <div v-for="t in battle.tanks" class="role" @click="controlPlayer(t)" @touchstart.prevent="controlPlayer(t)">
                <div class="name">{{ t.role.player.name }}</div>
                <div class="life">{{ t.life }}</div>
                <div class="watch" :class="{ watching: watchTank.role.player.id === t.role.player.id }" v-if="tank.life === 0 && t.life > 0">观战</div>
                <div class="relive" v-if="battle.status === 'fighting' && tank.life >= 3 && t.life === 0">复活</div>
            </div>
        </div>
        <div class="container">
            <canvas id="map" :width="multi * 208" :height="multi * 208"></canvas>
        </div>
        <!--<span>fps: {{ fps }}<br/>frames: {{ frames }}ms</span>-->
        <div class="roles">
            <div class="left"><div class="name">剩余:</div><div class="current">{{ leftEnemies }}</div><div class="total">{{ totalEnemies }}</div></div>
            <div class="left"><div class="name">STAGE</div><div class="current">{{ board.stage }}</div></div>
            <div v-for="t in battle.tanks" class="role" @click="controlPlayer(t)" @touchstart.prevent="controlPlayer(t)">
                <div class="name">{{ t.role.player.name }}</div>
                <div class="life">{{ t.life }}</div>
                <div class="watch" :class="{ watching: watchTank.role.player.id === t.role.player.id }" v-if="tank.life === 0 && t.life > 0">观战</div>
                <div class="relive" v-if="battle.status === 'fighting' && tank.life >= 3 && t.life === 0">复活</div>
            </div>
        </div>
    </div>
</template>
<style lang="less">
    @import "index";

    .game {
        background-color: #7f7f7e;
        > .board {
            .full;
            .flex;
            .flex-align(center, flex-start);
            > .container {
                width: 100vmin;
                height: 100vmin;
                > .choose {

                }
                > #map {
                    width: 100vmin;
                    height: 100vmin;
                    background-color: black;
                    color: white;
                }
            }
            > .roles {
                padding: 0.75em;
                .flex-1;
                .wrap;
            }
        }
        .role {
             margin: 0.25em;
             word-break: keep-all;
             white-space: nowrap;
             border-radius: 0.25em;
            line-height: 1.25em;
            overflow: hidden;
             .flex-row;
             .flex-align(flex-start, center);
             > .name {
                 padding: 0.25em 0.5em;
                 background-color: skyblue;
             }
             > .life {
                 padding: 0.25em 0.5em;
                 background-color: pink;
             }
             > .watch {
                padding: 0.25em 0.5em;
                background-color: #ff66ff;
                 &.watching {
                     background-color: #6666ff;
                     color: #ffffff;
                 }
             }
             > .relive {
                 padding: 0.25em 0.5em;
                 background-color: #66ff66;
                 color: #ffffff;
             }
        }
        .left {
            margin: 0.25em;
            word-break: keep-all;
            white-space: nowrap;
            border-radius: 0.25em;
            overflow: hidden;
            line-height: 1.5em;
            .flex-row;
            .flex-align(flex-start, center);
            > .name {
                padding: 0.25em 0.5em;
                background-color: #ff6666;
            }
            > .current {
                padding: 0.25em 0.5em;
                background-color: pink;
            }
            > .total {
                padding: 0.25em 0.5em;
                background-color: #ff66ff;
            }
        }
        @media screen and (orientation: portrait) {
            > .board {
                .column;
                .flex-align(flex-start, center);
                > .roles {
                    .flex-row;
                    &:first-child {
                        display: none;
                    }
                    &:last-child {
                        .flex-align(center, flex-start);
                    }
                }
            }
        }
        @media screen and (orientation: landscape) {
            > .board {
                .row;
                .flex-align(center, flex-start);
                > .roles {
                    .flex-column;
                    &:first-child {
                        .flex-align(flex-start, flex-end);
                        > div:nth-child(even) {
                            display: none;
                        }
                    }
                    &:last-child {
                        .flex-align(flex-start, flex-start);
                        > div:nth-child(odd) {
                            display: none;
                        }
                    }
                }
            }
        }
    }
</style>
<script>
    import { mixin, utils, events } from 'board-game/client';
    import { game as config } from '../config';
    import $ from 'jquery';

    class AudioMap {
        constructor(mp3, ogg, volume = 1) {
            this.mp3 = mp3;
            this.ogg = ogg;
            this.volume = volume;
            this.map = new Map();
            this.temp = getAudio(this.mp3, this.ogg, this.volume);
        }
        get(key) {
            if (this.map.has(key)) {
                return this.map.get(key);
            } else {
                let audio = this.getAudio();
                this.map.set(key, audio);
                return audio;
            }
        }
        init(key) {
            if (!this.map.has(key)) {
                this.map.set(key, this.getAudio());
            }
        }
        getAudio() {
            let temp = this.temp;
            this.temp = getAudio(this.mp3, this.ogg, this.volume);
            return temp;
        }
        remove(key) {
            if (this.map.has(key)) {
                $(this.map.get(key)).remove();
                this.map.delete(key);
            }
        }
    }

    const getImage = data => {
        let image = new Image();
        image.src = data;
        return image;
    };
    const getAudio = (mp3, ogg, volume = 1) => {
        let audio = utils.getAudio(mp3, ogg);
        audio.volume = volume;
        return audio;
    };
    const getAudioMap = (mp3, ogg, volume = 1) => {
        return new AudioMap(mp3, ogg, volume);
    };

    const getMulti = () => Math.max(1, Math.round(utils.vmin / 200));

    const iMap = getImage(require('./images/map.png'));

    const iMaps = new Array(18).fill(true).map((v, y) => [0, 1].map(x => {
        let canvas = document.createElement('canvas');
        canvas.width = canvas.height = 8;
        let context = canvas.getContext('2d');
        if (iMap.complete) {
            context.drawImage(iMap, x * 8, y * 8, 8, 8, 0, 0, 8, 8)
        } else {
            iMap.addEventListener('load', () => context.drawImage(iMap, x * 8, y * 8, 8, 8, 0, 0, 8, 8));
        }
        return canvas;
    }));

    const iTankSelf = getImage(require('./images/tank_self.png'));
    const iTankOther = getImage(require('./images/tank_other.png'));
    const iTankEnemy = getImage(require('./images/tank_enemy.png'));
    const iTankStatus = getImage(require('./images/status.png'));
    const iHome = getImage(require('./images/home.png'));
    const iBullet = getImage(require('./images/bullet.png'));
    const iBoom = getImage(require('./images/boom.png'));
    const iBorn = getImage(require('./images/born.png'));
    const iDeath = getImage(require('./images/death.png'));
    const iWord = getImage(require('./images/word.png'));
    const iGift = getImage(require('./images/gift.png'));

    const aGiftDefault = getAudio(require('./sounds/gift.default.mp3'), require('./sounds/gift.default.ogg'));
    const aGiftBoom = getAudio(require('./sounds/gift.boom.mp3'), require('./sounds/gift.boom.ogg'));
    const aGiftLife = getAudio(require('./sounds/gift.life.mp3'), require('./sounds/gift.life.ogg'));
    const aOpening = getAudio(require('./sounds/opening.mp3'), require('./sounds/opening.ogg'));
    const aGift = getAudio(require('./sounds/gift.mp3'), require('./sounds/gift.ogg'));
    const aKill = getAudioMap(require('./sounds/kill.mp3'), require('./sounds/kill.ogg'));
    const aBulletBlock = getAudioMap(require('./sounds/bullet.block.mp3'), require('./sounds/bullet.block.ogg'));
    const aBulletDestroy = getAudioMap(require('./sounds/bullet.destroy.mp3'), require('./sounds/bullet.destroy.ogg'));
    const aBulletErase = getAudioMap(require('./sounds/bullet.erase.mp3'), require('./sounds/bullet.erase.ogg'));
    const aBulletShoot = getAudioMap(require('./sounds/bullet.shoot.mp3'), require('./sounds/bullet.shoot.ogg'));
    const aPlayerMove = getAudioMap(require('./sounds/player.move.mp3'), require('./sounds/player.move.ogg'), 0.15);
    const aEnemyMove = getAudio(require('./sounds/enemy.move.mp3'), require('./sounds/enemy.move.ogg'), 0.15);
    const aEnemyHit = getAudioMap(require('./sounds/enemy.hit.mp3'), require('./sounds/enemy.hit.ogg'));
    const aHomeBoom = getAudio(require('./sounds/home.boom.mp3'), require('./sounds/home.boom.ogg'));
    const aScore = getAudio(require('./sounds/score.mp3'), require('./sounds/score.ogg'), 0.25);
    const aStar = getAudio(require('./sounds/star.mp3'), require('./sounds/star.ogg'));

    const ignoreTick = 5;

    const startPlay = (audio, ms, loop, param) => {
        if (audio instanceof AudioMap) {
            let map = audio;
            let key = ms;
            audio = map.get(key);
            audio.onComplete(() => map.remove(key));
            startPlay(audio, loop, param);
        } else {
            if (typeof ms === 'undefined') {
                if (audio.paused) audio.play(0);
            } else {
                if (audio.duration === 0) return;
                if (loop) ms = ms % Math.ceil(audio.duration * 1000);
                if (!audio.paused && ms <= audio.currentTime * 1000 + ignoreTick * config.tick || audio.duration * 1000 < ms) return;
                if (ms < config.tick * ignoreTick) ms = 0;
                audio.play(ms / 1000);
            }
        }
    };
    const stopPlay = audio => audio.pause();

    const colors = {
        black: 0,
        white: 1,
        red: 2,
        yellow: 3
    };

    const drawWords = (context, words, wordSize, color, location, center = false) => {
        if (Array.isArray(words)) {
            for (let [index, line] of words.entries()) {
                drawWords(context, line, wordSize, color, {
                    x: location.x,
                    y: (center === true || center.y === true ? location.y - (words.length - 1) * wordSize / 2 : location.y) + index * wordSize
                }, center);
            }
        } else {
            words = words.split(/\r\n/ig);
            if (words.length === 1) {
                words = words[0];
                let x = center === true || center.x === true ? location.x - words.length * wordSize / 2 : location.x;
                let y = center === true || center.y === true ? location.y - wordSize / 2 : location.y;
                for (let i = 0; i < words.length; i++) {
                    context.drawImage(iWord, words.charCodeAt(i) * 8, color * 8, 8, 8, x + i * wordSize, y, wordSize, wordSize);
                }
            } else {
                drawWords(context, words, wordSize, color, location, center);
            }
        }
    };

    const drawName = (context, tank, location, self, cellSize, fill, stroke) => {
        let fontWidth = context.measureText(tank.role.player.name).width;
        if (stroke) {
            context.strokeStyle = stroke;
            context.lineWidth = 1.5;
            context.strokeText(tank.role.player.name, location.x + cellSize - fontWidth / 2, location.y + (tank.location.y >= (self.life > 0 ? Infinity : self.location.y) ? 0 : cellSize * 2.5));
        }
        context.fillStyle = fill;
        context.fillText(tank.role.player.name, location.x + cellSize - fontWidth / 2, location.y + (tank.location.y >= (self.life > 0 ? Infinity : self.location.y) ? 0 : cellSize * 2.5));
    }

    const padLeft = (text, length, char = ' ') => {
        while (text.length < length) text = char + text;
        return text;
    };

    export default {
        mixins: [ mixin ],
        mounted() {
            this.draw();
            let resize = () => $('#map').width(utils.vmin).height(utils.vmin);
            $(window).on('resize', resize);
            resize();
        },
        beforeDestroy() {
            clearTimeout(this.timer);
        },
        data() {
            return {
                multi: getMulti(),
                up: false,
                down: false,
                right: false,
                left: false,
                shot: false,
                timer: 0,
                shotTimer: 0,
                fps: 0,
                frames: 0,
                ticks: [],
                asArray: 0,
                watchId: false,
                old: false
            }
        },
        computed: {
            battle() {
                return this.game && this.game.battle;
            },
            map() {
                return this.board && this.board.map;
            },
            tank() {
                return this.battle && this.battle.tanks.find(tank => tank.role.player.id === this.player.id);
            },
            deadTanks() {
                return this.battle.tanks.filter(tank => tank.life > 0);
            },
            watching() {
                return this.battle.replay || this.tank.life === 0;
            },
            watchTank() {
                if (this.battle.replay) {
                    return this.battle.tanks.find(tank => tank.role.player.id === this.battle.replay);
                } else if (this.tank.life > 0) {
                    return this.tank;
                } else {
                    if (this.watchId) {
                        let tank = this.battle.tanks.find(tank => tank.role.player.id === this.watchId);
                        if (tank.life > 0) {
                            return tank;
                        }
                    }
                    let first = this.battle.tanks.find(tank => tank.life > 0);
                    if (first) {
                        this.watchId = first.role.player.id;
                        return first;
                    }
                }
                return this.tank;
            },
            leftEnemies() {
                return this.battle && this.battle.enemies.filter(tank => tank.life > 0).length || 0;
            },
            totalEnemies() {
                return this.battle && this.battle.enemies.length || 0;
            }
        },
        methods: {
            resize() {
                this.multi = getMulti();
            },
            [[events.key.start.w, events.key.start.up, events.control.start.up]]() {
                this.upStart();
            },
            [[events.key.start.d, events.key.start.right, events.control.start.right]]() {
                this.rightStart();
            },
            [[events.key.start.s, events.key.start.down, events.control.start.down]]() {
                this.downStart();
            },
            [[events.key.start.a, events.key.start.left, events.control.start.left]]() {
                this.leftStart();
            },
            [[events.key.start.enter, events.key.start.space, events.control.start.a]]() {
                this.shotStart();
            },
            [[events.key.end.w, events.key.end.up, events.control.end.up]]() {
                this.upEnd();
            },
            [[events.key.end.d, events.key.end.right, events.control.end.right]]() {
                this.rightEnd();
            },
            [[events.key.end.s, events.key.end.down, events.control.end.down]]() {
                this.downEnd();
            },
            [[events.key.end.a, events.key.end.left, events.control.end.left]]() {
                this.leftEnd();
            },
            [[events.key.end.enter, events.key.end.space, events.control.end.a]]() {
                this.shotEnd();
            },
            controlPlayer(tank) {
                if (this.tank.life === 0 && tank.life > 0) {
                    this.watchPlayer(tank);
                } else if (this.battle.status === 'fighting' && this.tank.life >= 3 && tank.life === 0) {
                    this.relive(tank);
                }
            },
            watchPlayer(tank) {
                this.watchId = tank.role.player.id;
            },
            relive(tank) {
                this.$root.emit.relive(tank.role.player.id);
            },
            draw: function () {
                try {
                    let time = +Date.now();
                    let context = document.getElementById('map').getContext('2d');
                    let width = 208 * this.multi;
                    let height = 208 * this.multi;
                    let wordSize = 8 * this.multi;
                    let cellSize = 8 * this.multi;
                    let tankSize = cellSize * 2;
                    let boomSize = cellSize * 2;
                    let deathSize = tankSize * 2;
                    let bulletSize = cellSize / 2;
                    let giftSize = cellSize * 2;
                    context.setTransform(1, 0, 0, 1, 0, 0);
                    context.clearRect(0, 0, width, height);
                    context.imageSmoothingEnabled = false;
                    if (this.battle.status === 'opening' || this.battle.status === 'fighting' || this.battle.status === 'settling' || this.battle.status === 'replaying') {
                        if (this.battle.status === 'opening') {
                            startPlay(aOpening, this.battle.tick * config.tick);
                        }
                        let getLocation = tank => ({
                            x: (tank.location.x - 1 + (tank.status === 'moving' ? tank.direction === config.direction.right ? tank.percent / 100 : tank.direction === config.direction.left ? -tank.percent / 100 : 0 : 0)) * cellSize,
                            y: (tank.location.y - 1 + (tank.status === 'moving' ? tank.direction === config.direction.top ? -tank.percent / 100 : tank.direction === config.direction.bottom ? tank.percent / 100 : 0 : 0)) * cellSize
                        });
                        let translate = getLocation(this.watchTank);
                        if (this.old) {
                            translate = {
                                x: Math.ceil((translate.x - this.old.x) / 4 + this.old.x),
                                y: Math.ceil((translate.y - this.old.y) / 4 + this.old.y)
                            };
                        }
                        this.old = translate;
                        let {left, right, top, bottom} = this.battle.border;
                        context.translate(Math.max(-(right - 26) * cellSize, Math.min(-left * cellSize, 12.5 * cellSize - cellSize - translate.x)), Math.max(-(bottom - 26) * cellSize, Math.min(-top * cellSize, 12.5 * cellSize - cellSize - translate.y)));
                        let mapIndex = parseInt(this.battle.tick / config.preTick * 2) % 2;
                        for (let y = top, h = top * cellSize; y < bottom; y++, h += cellSize) {
                            let line = this.map[y];
                            for (let x = left, w = left * cellSize; x < right; x++, w += cellSize) {
                                let cell = line[x];
                                if (cell !== 7 && cell !== 0 && cell !== 99 && cell !== 98) {
                                    context.drawImage(iMaps[cell][mapIndex], 0, 0, 8, 8, w, h, cellSize, cellSize);
                                }
                            }
                        }

                        if (this.battle.protect && this.battle.protect <= 5 * config.preTick) {
                            config.wrapper.forEach(({y, x}) => {
                                if (this.map[y][x]) {
                                    if (parseInt(this.battle.protect / 10) % 2) {
                                        context.drawImage(iMaps[6][mapIndex], 0, 0, 8, 8, x * cellSize, y * cellSize, cellSize, cellSize);
                                    } else {
                                        context.drawImage(iMaps[1][mapIndex], 0, 0, 8, 8, x * cellSize, y * cellSize, cellSize, cellSize);
                                    }
                                }
                            });
                        }
                        context.font = cellSize + "px Arial";
                        for (let tank of this.battle.tanks) {
                            if (tank.status !== 'waiting' && (tank.life > 0 || tank.status === 'death' && tank.tick < config.wait.death)) {
                                let location = getLocation(tank);
                                if (tank.ship) context.drawImage(iTankStatus, (tank.tick % 2) * 16, 16, 16, 16, location.x, location.y, tankSize, tankSize);
                                if (tank.status === 'born') {
                                    context.drawImage(iBorn, (parseInt(tank.tick / 2) % 6) * 16, 0, 16, 16, location.x, location.y, tankSize, tankSize);
                                } else if (tank.status === 'death') {
                                    if (tank.tick < config.wait.death) {
                                        context.drawImage(iDeath, parseInt(tank.tick / 2) * 32, 0, 32, 32, tank.location.x * cellSize - deathSize / 2, tank.location.y * cellSize - deathSize / 2, deathSize, deathSize);
                                        startPlay(aKill, tank, tank.tick * config.tick);
                                    }
                                } else {
                                    if (tank.status === 'moving' || tank.status === 'original') {
                                        startPlay(aPlayerMove, tank, tank.tick * config.tick, true);
                                    }
                                    if (tank.status !== 'immobilize' || tank.tick % 10 !== 0) {
                                        context.drawImage(tank.role.player.id === this.player.id ? iTankSelf : iTankOther, Math.min(3, tank.fire - 1) * 32 + (tank.status === 'moving' || tank.status === 'original' ? (parseInt(tank.tick / 2) % 2) * 16 : 0), tank.direction * 16, 16, 16, location.x, location.y, tankSize, tankSize);
                                    }
                                }
                                if (tank.safe) context.drawImage(iTankStatus, (tank.tick % 2) * 16, 0, 16, 16, location.x, location.y, tankSize, tankSize);

                                context.font = cellSize + "px Arial";
                                if (!this.tank || this.tank.role.player.id !== tank.role.player.id) {
                                    if (this.tank.life > 0) {
                                        if (tank.role.player.id === this.battle.replay) {
                                            drawName(context, tank, location, this.tank, cellSize, '#0000a0ff', '#a0a0a080');
                                        } else if (tank.role.player.id === this.tank.enemy) {
                                            drawName(context, tank, location, this.tank, cellSize, '#a00000ff', '#a0a0a080');
                                        } else if (
                                            !this.battle.replay && Math.abs(tank.location.x - this.tank.location.x) <= 12 && Math.abs(tank.location.y - this.tank.location.y) <= 12
                                        ) {
                                            drawName(context, tank, location, this.tank, cellSize, '#ffffffcc');
                                        }
                                    } else if (this.tank.life === 0) {
                                        drawName(context, tank, location, this.tank, cellSize, '#ffffffcc');
                                    }
                                }
                            }
                            if (tank.bullets) {
                                for (let bullet of tank.bullets) {
                                    startPlay(aBulletShoot, bullet, bullet.tick * config.tick);
                                    if (bullet.boom) {
                                        if (bullet.boom < config.wait.boom) {
                                            if (bullet.end === 'block') {
                                                startPlay(aBulletBlock, bullet, bullet.boom * config.tick);
                                            } else if (bullet.end === 'erase') {
                                                startPlay(aBulletErase, bullet, bullet.boom * config.tick);
                                            } else if (bullet.end === 'destroy') {
                                                startPlay(aBulletDestroy, bullet, bullet.boom * config.tick);
                                            }
                                            let x = bullet.direction === config.direction.right ? Math.floor(bullet.location.x / 100) : bullet.direction === config.direction.left ? Math.ceil(bullet.location.x / 100) : Math.round(bullet.location.x / 100);
                                            let y = bullet.direction === config.direction.top ? Math.ceil(bullet.location.y / 100) : bullet.direction === config.direction.bottom ? Math.floor(bullet.location.y / 100) : Math.round(bullet.location.y / 100);
                                            context.drawImage(iBoom, parseInt(bullet.boom / 2) * 16, 0, 16, 16, x * cellSize - cellSize, y * cellSize - cellSize, boomSize, boomSize);
                                        } else {
                                            if (bullet.end === 'blocked') {
                                                startPlay(aBulletBlock, bullet, (bullet.boom - config.wait.boom) * config.tick);
                                            }
                                        }
                                    } else {
                                        context.drawImage(iBullet, 0, bullet.direction * 4, 4, 4, bullet.location.x / 100 * cellSize - bulletSize / 2, bullet.location.y / 100 * cellSize - bulletSize / 2, bulletSize, bulletSize);
                                    }
                                }
                            }
                        }
                        for (let tank of this.battle.enemies) {
                            if (tank.status !== 'waiting' && tank.life > 0) {
                                let location = getLocation(tank);
                                if (tank.status === 'born') {
                                    context.drawImage(iBorn, (parseInt(tank.tick / 2) % 6) * 16, 0, 16, 16, location.x, location.y, tankSize, tankSize);
                                } else {
                                    if (tank.gift) {
                                        if (parseInt(tank.tick / 2) % 2 === 0) {
                                            context.drawImage(iTankEnemy, (tank.status === 'moving' || tank.status === 'original' ? (parseInt(tank.tick / 2) % 2) * 16 : 0) + 32, tank.role * 64 + tank.direction * 16, 16, 16, location.x, location.y, tankSize, tankSize);
                                        } else {
                                            context.drawImage(iTankEnemy, (tank.status === 'moving' || tank.status === 'original' ? (parseInt(tank.tick / 2) % 2) * 16 : 0), tank.role * 64 + tank.direction * 16, 16, 16, location.x, location.y, tankSize, tankSize);
                                        }
                                    } else {
                                        context.drawImage(iTankEnemy, (tank.status === 'moving' || tank.status === 'original' ? (parseInt(tank.tick / 2) % 2) * 16 : 0) + (tank.life > 1 ? (tank.life % 3 + 2) : 0) * 32, tank.role * 64 + tank.direction * 16, 16, 16, location.x, location.y, tankSize, tankSize);
                                    }
                                    if (tank.status !== 'death') {
                                        if (tank.status === 'moving' || tank.status === 'original') {
                                            startPlay(aEnemyMove, undefined, true);
                                        }
                                    }
                                }
                            } else if (tank.status === 'death') {
                                startPlay(aKill, tank, tank.tick * config.tick);
                                if (tank.tick < config.wait.death) {
                                    context.drawImage(iDeath, parseInt(tank.tick / 2) * 32, 0, 32, 32, tank.location.x * cellSize - deathSize / 2, tank.location.y * cellSize - deathSize / 2, deathSize, deathSize);
                                }
                            }
                            if (tank.bullets) {
                                for (let bullet of tank.bullets) {
                                    if (bullet.boom) {
                                        if (bullet.boom < config.wait.boom) {
                                            let x = bullet.direction === config.direction.right ? Math.floor(bullet.location.x / 100) : bullet.direction === config.direction.left ? Math.ceil(bullet.location.x / 100) : Math.round(bullet.location.x / 100);
                                            let y = bullet.direction === config.direction.top ? Math.ceil(bullet.location.y / 100) : bullet.direction === config.direction.bottom ? Math.floor(bullet.location.y / 100) : Math.round(bullet.location.y / 100);
                                            context.drawImage(iBoom, parseInt(bullet.boom / 2) * 16, 0, 16, 16, x * cellSize - cellSize, y * cellSize - cellSize, boomSize, boomSize);
                                        } else {
                                            if (bullet.end === 'blocked') {
                                                startPlay(aEnemyHit, bullet, (bullet.boom - config.wait.boom) * config.tick);
                                            }
                                        }
                                    } else {
                                        context.drawImage(iBullet, 0, bullet.direction * 4, 4, 4, bullet.location.x / 100 * cellSize - bulletSize / 2, bullet.location.y / 100 * cellSize - bulletSize / 2, bulletSize, bulletSize);
                                    }
                                }
                            }
                        }
                        for (let y = top, h = top * cellSize; y < bottom; y++, h += cellSize) {
                            let line = this.map[y];
                            for (let x = left, w = left * cellSize; x < right; x++, w += cellSize) {
                                let cell = line[x];
                                if (cell === 7) {
                                    context.drawImage(iMaps[cell][mapIndex], 0, 0, 8, 8, w, h, cellSize, cellSize);
                                }
                            }
                        }
                        let homeSize = cellSize * 2;
                        if (this.battle.home.status === 'life') {
                            context.drawImage(iHome, 0, 0, 16, 16, 24 * cellSize, 24 * cellSize, homeSize, homeSize);
                        } else {
                            if (this.battle.home.tick < config.wait.death) {
                                startPlay(aHomeBoom, this.battle.home.tick * config.tick);
                                context.drawImage(iDeath, parseInt(this.battle.home.tick / 2) * 32, 0, 32, 32, 23 * cellSize, 23 * cellSize, deathSize, deathSize);
                            } else {
                                context.drawImage(iHome, 16, 0, 16, 16, 24 * cellSize, 24 * cellSize, homeSize, homeSize);
                            }
                        }
                        for (let gift of this.battle.gifts) {
                            if (gift.got) {
                                if (gift.type === config.gift.life) {
                                    startPlay(aGiftLife, (config.wait.getGiftFrame - gift.tick) * config.tick);
                                } else if (gift.type === config.gift.boom) {
                                    startPlay(aGiftBoom, (config.wait.getGiftFrame - gift.tick) * config.tick);
                                } else {
                                    startPlay(aGiftDefault, (config.wait.getGiftFrame - gift.tick) * config.tick);
                                }
                            } else {
                                startPlay(aGift, (config.wait.giftFrame - gift.tick) * config.tick);
                            }
                            if (gift.got) {
                                drawWords(context, '500', wordSize, colors.white, {
                                    x: gift.location.x * cellSize - cellSize,
                                    y: gift.location.y * cellSize - giftSize + (gift.tick / config.wait.getGiftFrame) * cellSize
                                }, true);
                            } else if (parseInt(this.battle.tick / 4) % 2) {
                                context.drawImage(iGift, 16 * gift.type, 0, 16, 16, gift.location.x * cellSize - cellSize, gift.location.y * cellSize - cellSize, giftSize, giftSize);
                            }
                        }
                    }
                    if (this.battle.status === 'opening') {
                        context.setTransform(1, 0, 0, 1, 0, 0);
                        context.fillStyle = "#7f7f7e";
                        let percent = Math.min(config.wait.open * config.preTick - this.battle.tick, this.battle.tick) / config.preTick;
                        context.fillRect(0, 0, width, percent * height);
                        context.fillRect(0, height - percent * height, width, percent * height);
                        if (percent >= 0.5) {
                            drawWords(context, `STAGE  ${this.board.stage}`, wordSize, colors.black, {
                                x: width / 2,
                                y: height / 2
                            }, true);
                        }
                    }
                    if (this.battle.over && this.battle.status !== 'ending') {
                        context.setTransform(1, 0, 0, 1, 0, 0);
                        drawWords(context, 'GAME\r\nOVER', wordSize, colors.red, {
                            x: width / 2,
                            y: Math.max(height / 4, height - this.battle.tick * cellSize / 4)
                        }, true);
                    }
                    if (this.battle.status === 'score' || this.battle.status === 'ending') {
                        stopPlay(aEnemyMove);
                        //draw page header
                        drawWords(context, `STAGE ${this.battle.status === 'score' ? this.board.stage - 1 : this.board.stage}`, wordSize, colors.white, {
                            x: width / 2,
                            y: cellSize
                        }, true);
                        //draw table header
                        let lineSize = cellSize / 2;
                        let spaceSize = lineSize / 2;
                        let columns = {
                            player: 8,
                            tank: 3,
                            current: 6,
                            score: 7
                        };
                        let left = cellSize * columns.player + lineSize;
                        drawWords(context, 'PLAYER', wordSize, colors.red, {
                            x: columns.player / 2 * cellSize,
                            y: cellSize * 3
                        }, true);
                        for (let i = 0; i < 4; i++) {
                            context.drawImage(iTankEnemy, 0, i * 64, 16, 16, left + i * (columns.tank * cellSize + lineSize), cellSize * 2, tankSize, tankSize);
                        }
                        context.drawImage(iGift, 144, 0, 16, 16, left + 4 * (columns.tank * cellSize + lineSize), cellSize * 2, giftSize, giftSize);

                        context.moveTo(0, cellSize * 2 + tankSize + spaceSize);
                        context.lineTo(width, cellSize * 2 + tankSize + spaceSize);
                        context.strokeStyle = "#fffffc";
                        context.lineWidth = spaceSize;
                        context.stroke();
                        //draw each tank
                        context.font = cellSize + "px Arial";
                        this.battle.tanks.forEach((tank, index) => {
                            if (tank.status === 'score') {
                                let top = cellSize * 2 + tankSize + spaceSize + index * (wordSize + spaceSize) * 2;
                                //draw player name
                                context.fillStyle = this.battle.tick < config.score.wait * (config.score.enemies.length + 1) ? '#fffffc' : tank.score.first ? '#ff5000' : '#fffffc';
                                context.fillText(tank.role.player.name, cellSize, top + wordSize);
                                (tank.score.enemies || []).slice(0, Math.ceil(this.battle.tick / config.score.wait)).forEach((value, i) => {
                                    drawWords(context, padLeft(this.battle.tick >= (i + 1) * config.score.wait ? value.toString() : this.battle.tick % config.score.wait < config.score.tick ? Math.floor(Math.random() * 10).toString() : value.toString(), 2), wordSize, colors.white, {
                                        x: left + i * (columns.tank * cellSize + lineSize),
                                        y: top
                                    }, false);
                                });
                                if (this.battle.tick > config.score.wait * config.score.enemies.length) {
                                    drawWords(context, padLeft(this.battle.tick >= config.score.wait * config.score.enemies.length + config.score.tick ? tank.score.gifts.toString() : Math.floor(Math.random() * 10).toString(), 2), wordSize, colors.white, {
                                        x: left + 4 * (columns.tank * cellSize + lineSize),
                                        y: top
                                    }, false);
                                }

                                drawWords(context, padLeft('TOTAL:', columns.player), wordSize, colors.red, {
                                    x: 0,
                                    y: top + wordSize + spaceSize
                                });
                                drawWords(context, padLeft(tank.score.value.toString(), columns.score), wordSize, colors.yellow, {
                                    x: left,
                                    y: top + wordSize + spaceSize
                                });
                                drawWords(context, padLeft('(+' + tank.score.current.toString() + ')', columns.score + 3), wordSize, colors.white, {
                                    x: width - wordSize * (3 + columns.score),
                                    y: top + wordSize + spaceSize
                                });
                            }
                        });
                        let scoreTick = this.battle.tick - config.score.wait * (config.score.enemies.length + 1);
                        if (scoreTick < 0) {
                            let tick = this.battle.tick % config.score.wait;
                            startPlay(aScore, tick * config.tick, tick < config.score.tick);
                        } else {
                            startPlay(aStar, scoreTick * config.tick);
                        }
                    }
                    let now = Date.now();
                    while (this.ticks.length && this.ticks[0] <= now - 1000) this.ticks.shift();
                    this.ticks.push(now);
                    time = now - time;
                    if (time >= 35) console.log('use ' + time + 'ms');
                    this.frames = padLeft(time + '', 3, '0');
                    this.fps = this.ticks.length;
                    this.timer = setTimeout(() => this.draw(), 40 - time % 40);
                } catch (e) {
                    $(document.body).prepend(`<span style="color: white">${e}</span>`);
                }
            },
            upStart() {
                if (!this.up) {
                    this.up = true;
                    this.$root.emit.directionStart(0);
                }
            },
            rightStart() {
                if (!this.right) {
                    this.right = true;
                    this.$root.emit.directionStart(1);
                }
            },
            downStart() {
                if (!this.down) {
                    this.down = true;
                    this.$root.emit.directionStart(2);
                }
            },
            leftStart() {
                if (!this.left) {
                    this.left = true;
                    this.$root.emit.directionStart(3);
                }
            },
            shotStart() {
                if (!this.shot) {
                    this.shot = true;
                    this.doShot();
                }
            },
            upEnd() {
                if (this.up) {
                    this.up = false;
                    this.$root.emit.directionEnd(0);
                }
            },
            rightEnd() {
                if (this.right) {
                    this.right = false;
                    this.$root.emit.directionEnd(1);
                }
            },
            downEnd() {
                if (this.down) {
                    this.down = false;
                    this.$root.emit.directionEnd(2);
                }
            },
            leftEnd() {
                if (this.left) {
                    this.left = false;
                    this.$root.emit.directionEnd(3);
                }
            },
            shotEnd() {
                if (this.shot) {
                    this.shot = false;
                    clearTimeout(this.shotTimer);
                }
            },
            doShot() {
                this.$root.emit.shot();
                this.shotTimer = setTimeout(() => this.doShot(), (5 - config.bullet[this.tank.fire - 1].speed) * config.preTick);
            }
        }
    }
</script>
